import AppKit
import AXorcist
import Foundation
import os.log
import PeekabooFoundation
import PeekabooVisualizer

/// Dialog-specific errors
public enum DialogError: Error {
    case noActiveDialog
    case dialogNotFound
    case noFileDialog
    case buttonNotFound(String)
    case fieldNotFound
    case invalidFieldIndex
    case noTextFields
    case noDismissButton
}

/// Default implementation of dialog management operations
@MainActor
public final class DialogService: DialogServiceProtocol {
    private let logger = Logger(subsystem: "boo.peekaboo.core", category: "DialogService")
    private let dialogTitleHints = ["open", "save", "export", "import", "choose", "replace"]
    private let applicationService: any ApplicationServiceProtocol
    private let focusService = FocusManagementService()
    private let windowIdentityService = WindowIdentityService()

    // Visualizer client for visual feedback
    private let visualizerClient = VisualizationClient.shared

    public init(applicationService: (any ApplicationServiceProtocol)? = nil) {
        self.applicationService = applicationService ?? ApplicationService()
        self.logger.debug("DialogService initialized")
        // Connect to visualizer if available
        // Only connect to visualizer if we're not running inside the Mac app
        // The Mac app provides the visualizer service, not consumes it
        let isMacApp = Bundle.main.bundleIdentifier?.hasPrefix("boo.peekaboo.mac") == true
        if !isMacApp {
            self.logger.debug("Connecting to visualizer service (running as CLI/external tool)")
            self.visualizerClient.connect()
        } else {
            self.logger.debug("Skipping visualizer connection (running inside Mac app)")
        }
    }
}

@MainActor
extension DialogService {
    public func findActiveDialog(windowTitle: String?, appName: String?) async throws -> DialogInfo {
        self.logger.info("Finding active dialog")
        if let title = windowTitle {
            self.logger.debug("Looking for window with title: \(title)")
        }

        let element = try await self.resolveDialogElement(windowTitle: windowTitle, appName: appName)

        let title = element.title() ?? "Untitled Dialog"
        let role = element.role() ?? "Unknown"
        let subrole = element.subrole()

        // Check if it's a file dialog
        let isFileDialog = title.contains("Save") || title.contains("Open") ||
            title.contains("Export") || title.contains("Import")

        let position = element.position() ?? .zero
        let size = element.size() ?? .zero
        let bounds = CGRect(origin: position, size: size)

        let info = DialogInfo(
            title: title,
            role: role,
            subrole: subrole,
            isFileDialog: isFileDialog,
            bounds: bounds)

        self.logger.info("\(AgentDisplayTokens.Status.success) Found dialog: \(title), file dialog: \(isFileDialog)")
        return info
    }

    public func clickButton(
        buttonText: String,
        windowTitle: String?,
        appName: String?) async throws -> DialogActionResult
    {
        self.logger.info("Clicking button: \(buttonText)")
        if let title = windowTitle {
            self.logger.debug("In window: \(title)")
        }

        let dialog = try await self.resolveDialogElement(windowTitle: windowTitle, appName: appName)

        // Find buttons
        let buttons = self.collectButtons(from: dialog)
        self.logger.debug("Found \(buttons.count) buttons in dialog")

        // Find target button (exact match or contains)
        guard let targetButton = buttons.first(where: { btn in
            btn.title() == buttonText || btn.title()?.contains(buttonText) == true
        }) else {
            throw PeekabooError.elementNotFound("\(buttonText)")
        }

        let resolvedButtonTitle = targetButton.title() ?? buttonText

        // Get button bounds for visual feedback
        let buttonBounds: CGRect = if let position = targetButton.position(), let size = targetButton.size() {
            CGRect(origin: position, size: size)
        } else {
            .zero
        }

        // Show dialog interaction visual feedback
        if buttonBounds != .zero {
            _ = await self.visualizerClient.showDialogInteraction(
                element: .button,
                elementRect: buttonBounds,
                action: .clickButton)
        }

        // Click the button
        self.logger.debug("Clicking button: \(resolvedButtonTitle)")
        try targetButton.performAction(.press)

        let result = DialogActionResult(
            success: true,
            action: .clickButton,
            details: [
                "button": resolvedButtonTitle,
                "window": dialog.title() ?? "Dialog",
            ])

        self.logger
            .info(
                "\(AgentDisplayTokens.Status.success) Successfully clicked button: \(resolvedButtonTitle)")
        return result
    }

    public func enterText(
        text: String,
        fieldIdentifier: String?,
        clearExisting: Bool,
        windowTitle: String?,
        appName: String?) async throws -> DialogActionResult
    {
        self.logger.info("Entering text into dialog field")
        self.logger.debug("Text length: \(text.count) chars, clear existing: \(clearExisting)")
        if let identifier = fieldIdentifier {
            self.logger.debug("Target field: \(identifier)")
        }

        let dialog = try await self.resolveDialogElement(windowTitle: windowTitle, appName: appName)
        let textFields = self.collectTextFields(from: dialog)
        self.logger.debug("Found \(textFields.count) text fields")

        guard !textFields.isEmpty else {
            self.logger.error("No text fields found in dialog")
            throw PeekabooError.operationError(message: "No text fields found in dialog.")
        }

        let targetField = try self.selectTextField(
            in: textFields,
            identifier: fieldIdentifier)

        await self.highlightDialogElement(
            element: .textField,
            bounds: self.elementBounds(for: targetField),
            action: .enterText)

        try self.focusTextField(targetField)
        try self.clearFieldIfNeeded(targetField, shouldClear: clearExisting)
        try self.typeTextValue(text, delay: 10000)

        let result = DialogActionResult(
            success: true,
            action: .enterText,
            details: [
                "field": targetField.title() ?? "Text Field",
                "text_length": String(text.count),
                "cleared": String(clearExisting),
            ])

        self.logger.info("\(AgentDisplayTokens.Status.success) Successfully entered text into field")
        return result
    }

    public func handleFileDialog(
        path: String?,
        filename: String?,
        actionButton: String = "Save",
        appName: String?) async throws -> DialogActionResult
    {
        self.logger.info("Handling file dialog")
        if let path {
            self.logger.debug("Path: \(path)")
        }
        if let filename {
            self.logger.debug("Filename: \(filename)")
        }
        self.logger.debug("Action button: \(actionButton)")

        await self.ensureDialogVisibility(windowTitle: nil, appName: appName)
        let dialog = try findFileDialogElement()
        var details: [String: String] = [:]

        if let filePath = path {
            try self.navigateToPath(filePath)
            details["path"] = filePath
        }

        if let fileName = filename {
            try self.updateFilename(fileName, in: dialog)
            details["filename"] = fileName
        }

        if try self.clickActionButton(named: actionButton, in: dialog) {
            details["button_clicked"] = actionButton
        }

        let result = DialogActionResult(
            success: true,
            action: .handleFileDialog,
            details: details)

        self.logger.info("\(AgentDisplayTokens.Status.success) Successfully handled file dialog")
        return result
    }

    public func dismissDialog(force: Bool, windowTitle: String?, appName: String?) async throws -> DialogActionResult {
        self.logger.info("Dismissing dialog (force: \(force))")

        if force {
            self.logger.debug("Force dismissing with Escape key")

            // Press Escape
            try? InputDriver.tapKey(.escape)

            self.logger.info("\(AgentDisplayTokens.Status.success) Dialog dismissed with Escape key")
            return DialogActionResult(
                success: true,
                action: .dismiss,
                details: ["method": "escape"])
        } else {
            self.logger.debug("Looking for dismiss button")

            // Find and click dismiss button
            let dialog = try await self.resolveDialogElement(windowTitle: windowTitle, appName: appName)
            let buttons = dialog.children()?.filter { $0.role() == "AXButton" } ?? []
            self.logger.debug("Found \(buttons.count) buttons in dialog")

            // Look for common dismiss buttons
            let dismissButtons = ["Cancel", "Close", "Dismiss", "No", "Don't Save"]
            self.logger.debug("Looking for dismiss buttons: \(dismissButtons.joined(separator: ", "))")

            for buttonName in dismissButtons {
                if let button = buttons.first(where: { $0.title() == buttonName }) {
                    self.logger.debug("Found dismiss button: \(buttonName)")
                    try button.performAction(.press)

                    self.logger.info("\(AgentDisplayTokens.Status.success) Dialog dismissed by clicking: \(buttonName)")
                    return DialogActionResult(
                        success: true,
                        action: .dismiss,
                        details: [
                            "method": "button",
                            "button": buttonName,
                        ])
                }
            }

            self.logger.error("No dismiss button found in dialog")
            throw PeekabooError.operationError(message: "No dismiss button found in dialog.")
        }
    }

    public func listDialogElements(windowTitle: String?, appName: String?) async throws -> DialogElements {
        self.logger.info("Listing dialog elements")
        if let title = windowTitle {
            self.logger.debug("For window: \(title)")
        }

        let dialogInfo = try await findActiveDialog(windowTitle: windowTitle, appName: appName)
        let dialog = try await self.resolveDialogElement(windowTitle: windowTitle, appName: appName)

        let buttons = self.dialogButtons(from: dialog)
        let textFields = self.dialogTextFields(from: dialog)
        let staticTexts = self.dialogStaticTexts(from: dialog)
        let otherElements = self.dialogOtherElements(from: dialog)

        let accessoryRoles: Set<String> = [
            "AXCheckBox", "AXRadioButton", "AXPopUpButton", "AXComboBox", "AXSlider", "AXDisclosureTriangle",
        ]
        let hasAccessoryElements = otherElements.contains { accessoryRoles.contains($0.role) }
        let looksLikeDialog = self.isDialogElement(dialog, matching: windowTitle)
        let hasContent = !buttons.isEmpty ||
            !textFields.isEmpty ||
            !staticTexts.isEmpty ||
            hasAccessoryElements

        let isSuspiciousUnknown = dialogInfo.role == "AXWindow" && dialogInfo.subrole == "AXUnknown"
        if !hasContent, !looksLikeDialog || isSuspiciousUnknown {
            // We landed on a normal window (Finder, Chrome, etc.) and didn't find any dialog-specific
            // controls. Instead of returning an empty payload (which CLI users mistake for success),
            // throw so callers can prompt the user to open the desired sheet first.
            self.logger.error(
                """
                Active window '\(dialogInfo.title)' (role: \(dialogInfo.role)) is not a dialog and \
                contains no interactive elements
                """)
            throw DialogError.noActiveDialog
        }

        let elements = DialogElements(
            dialogInfo: dialogInfo,
            buttons: buttons,
            textFields: textFields,
            staticTexts: staticTexts,
            otherElements: otherElements)

        let summary = "\(AgentDisplayTokens.Status.success) Listed \(buttons.count) buttons, " +
            "\(textFields.count) fields, \(staticTexts.count) texts"
        self.logger.info("\(summary, privacy: .public)")
        return elements
    }
}

// MARK: - Private Helpers

@MainActor
extension DialogService {
    private func findDialogElement(withTitle title: String?, appName: String?) throws -> Element {
        self.logger.debug("Finding dialog element")

        let systemWide = Element.systemWide()

        if let focusedElement = systemWide.attribute(Attribute<Element>("AXFocusedUIElement")),
           let hostingWindow = focusedElement.attribute(Attribute<Element>("AXWindow")),
           let candidate = self.resolveDialogCandidate(in: hostingWindow, matching: title)
        {
            return candidate
        }

        if let focusedWindow = systemWide.attribute(Attribute<Element>("AXFocusedWindow")),
           let focusedCandidate = self.resolveDialogCandidate(in: focusedWindow, matching: title)
        {
            return focusedCandidate
        }

        var focusedAppElement: Element? = systemWide.attribute(Attribute<Element>("AXFocusedApplication")) ?? {
            if let frontmost = NSWorkspace.shared.frontmostApplication {
                return AXApp(frontmost).element
            }
            return nil
        }()

        if focusedAppElement == nil,
           let appName,
           let targetApp = self.runningApplication(matching: appName)
        {
            focusedAppElement = AXApp(targetApp).element
        }

        guard let focusedApp = focusedAppElement else {
            self.logger.error("No focused application found")
            throw PeekabooError.operationError(message: "No active dialog window found.")
        }

        let windows = focusedApp.windowsWithTimeout() ?? []
        self.logger.debug("Checking \(windows.count) windows for dialogs")

        for window in windows {
            if let candidate = self.resolveDialogCandidate(in: window, matching: title) {
                return candidate
            }
        }

        if let globalWindows = systemWide.windows() {
            for window in globalWindows {
                if let candidate = self.resolveDialogCandidate(in: window, matching: title) {
                    return candidate
                }
            }
        }

        for app in NSWorkspace.shared.runningApplications {
            let axApp = AXApp(app).element
            let appWindows = axApp.windowsWithTimeout() ?? []
            for window in appWindows {
                if let candidate = self.resolveDialogCandidate(in: window, matching: title) {
                    return candidate
                }
            }
        }

        if let cgCandidate = self.findDialogUsingCGWindowList(title: title) {
            return cgCandidate
        }

        throw DialogError.noActiveDialog
    }

    private func resolveDialogElement(windowTitle: String?, appName: String?) async throws -> Element {
        if let appName, !appName.isEmpty {
            self.logger.debug("Resolving dialog with app hint: \(appName)")
        }
        if let element = try? self.findDialogElement(withTitle: windowTitle, appName: appName) {
            return element
        }

        await self.ensureDialogVisibility(windowTitle: windowTitle, appName: appName)

        if let element = try? self.findDialogElement(withTitle: windowTitle, appName: appName) {
            return element
        }

        if let element = await self.findDialogViaApplicationService(windowTitle: windowTitle, appName: appName) {
            return element
        }

        throw DialogError.noActiveDialog
    }

    private func ensureDialogVisibility(windowTitle: String?, appName: String?) async {
        do {
            let applications = try await self.applicationService.listApplications()
            for app in applications.data.applications {
                if let appName,
                   app.name.caseInsensitiveCompare(appName) != .orderedSame,
                   app.bundleIdentifier?.caseInsensitiveCompare(appName) != .orderedSame
                {
                    continue
                }

                let windowsOutput = try await self.applicationService.listWindows(for: app.name, timeout: nil)
                if let window = windowsOutput.data.windows.first(where: {
                    self.matchesDialogWindowTitle($0.title, expectedTitle: windowTitle)
                }) {
                    self.logger.info("Focusing dialog candidate '\(window.title)' from \(app.name)")
                    try await self.focusService.focusWindow(
                        windowID: CGWindowID(window.windowID),
                        options: FocusManagementService.FocusOptions(
                            timeout: 1.0,
                            retryCount: 1,
                            switchSpace: true,
                            bringToCurrentSpace: true))
                    try await Task.sleep(nanoseconds: 200_000_000)
                    return
                }
            }
        } catch {
            self.logger.debug("Dialog visibility assist failed: \(String(describing: error))")
        }
    }

    @MainActor
    private func findDialogViaApplicationService(windowTitle: String?, appName: String?) async -> Element? {
        guard let applications = try? await self.applicationService.listApplications() else {
            return nil
        }

        let frontmostApp = NSWorkspace.shared.frontmostApplication
        let frontmostBundle = frontmostApp?.bundleIdentifier?.lowercased()
        let frontmostName = frontmostApp?.localizedName?.lowercased()

        for app in applications.data.applications {
            if let appName,
               app.name.caseInsensitiveCompare(appName) != .orderedSame,
               app.bundleIdentifier?.caseInsensitiveCompare(appName) != .orderedSame
            {
                continue
            }

            if let bundle = frontmostBundle,
               let candidateBundle = app.bundleIdentifier?.lowercased(),
               candidateBundle != bundle
            {
                continue
            }

            if frontmostBundle == nil,
               let name = frontmostName,
               app.name.lowercased() != name
            {
                continue
            }

            guard let windowsOutput = try? await self.applicationService.listWindows(for: app.name, timeout: nil) else {
                continue
            }

            guard let windowInfo = windowsOutput.data.windows.first(where: {
                self.matchesDialogWindowTitle($0.title, expectedTitle: windowTitle)
            }) else {
                continue
            }

            if let windowHandle = self.windowIdentityService.findWindow(byID: CGWindowID(windowInfo.windowID)),
               let candidate = self.resolveDialogCandidate(
                   in: windowHandle.element,
                   matching: windowTitle ?? windowInfo.title)
            {
                return candidate
            }

            guard let runningApp = NSRunningApplication(processIdentifier: app.processIdentifier) else { continue }
            let axApp = AXApp(runningApp).element
            guard let appWindows = axApp.windowsWithTimeout() else { continue }

            if let match = appWindows.first(where: {
                let title = $0.title() ?? ""
                return title == windowInfo.title ||
                    self.matchesDialogWindowTitle(title, expectedTitle: windowTitle)
            }) {
                return match
            }
        }

        return nil
    }

    @MainActor
    private func findFileDialogElement() throws -> Element {
        let systemWide = Element.systemWide()
        guard let focusedApp = systemWide.attribute(Attribute<Element>("AXFocusedApplication")) else {
            throw PeekabooError.operationError(message: "No active dialog window found.")
        }

        let windows = focusedApp.windows() ?? []

        for window in windows {
            if let candidate = self.resolveDialogCandidate(in: window, matching: nil),
               self.isFileDialogElement(candidate)
            {
                return candidate
            }
        }

        if let focusedWindow = systemWide.attribute(Attribute<Element>("AXFocusedWindow")),
           self.isFileDialogElement(focusedWindow)
        {
            return focusedWindow
        }

        throw PeekabooError.operationError(message: "No file dialog (Save/Open) found.")
    }

    @MainActor
    private func collectTextFields(from element: Element) -> [Element] {
        var fields: [Element] = []

        func collectFields(from el: Element) {
            if el.role() == "AXTextField" || el.role() == "AXTextArea" {
                fields.append(el)
            }

            if let children = el.children() {
                for child in children {
                    collectFields(from: child)
                }
            }
        }

        collectFields(from: element)
        return fields
    }

    private func selectTextField(in textFields: [Element], identifier: String?) throws -> Element {
        guard let identifier else {
            return textFields[0]
        }

        if let index = Int(identifier) {
            guard textFields.indices.contains(index) else {
                throw PeekabooError
                    .invalidInput("Invalid field index: \(index). Dialog has \(textFields.count) fields.")
            }
            return textFields[index]
        }

        guard let field = textFields.first(where: { field in
            field.title() == identifier ||
                field.attribute(Attribute<String>("AXPlaceholderValue")) == identifier ||
                field.descriptionText()?.contains(identifier) == true
        }) else {
            throw PeekabooError.elementNotFound("\(identifier)")
        }

        return field
    }

    private func elementBounds(for element: Element) -> CGRect {
        guard let position = element.position(), let size = element.size() else {
            return .zero
        }
        return CGRect(origin: position, size: size)
    }

    private func highlightDialogElement(
        element: DialogElementType,
        bounds: CGRect,
        action: DialogActionType) async
    {
        guard bounds != .zero else { return }
        _ = await self.visualizerClient.showDialogInteraction(
            element: element,
            elementRect: bounds,
            action: action)
    }

    private func focusTextField(_ field: Element) throws {
        self.logger.debug("Focusing text field")
        try field.performAction(.press)
    }

    private func clearFieldIfNeeded(_ field: Element, shouldClear: Bool) throws {
        guard shouldClear else { return }
        self.logger.debug("Clearing existing text")
        try? InputDriver.hotkey(keys: ["cmd", "a"])
        try? InputDriver.tapKey(.delete)
        usleep(50000)
    }

    private func typeTextValue(_ text: String, delay: useconds_t) throws {
        self.logger.debug("Typing text into field")
        try InputDriver.type(text, delayPerCharacter: Double(delay) / 1_000_000.0)
    }

    private func pressKey(_ virtualKey: CGKeyCode, modifiers: CGEventFlags = []) {
        if let special = self.mapSpecialKey(code: virtualKey) {
            var keys = self.modifierKeys(from: modifiers)
            keys.append(special)
            try? InputDriver.hotkey(keys: keys, holdDuration: 0.05)
            return
        }
        if let character = self.mapCharacter(code: virtualKey) {
            var keys = self.modifierKeys(from: modifiers)
            keys.append(character)
            try? InputDriver.hotkey(keys: keys, holdDuration: 0.05)
            return
        }
    }

    private func mapSpecialKey(code: CGKeyCode) -> String? {
        switch code {
        case 0x24: "return"
        case 0x35: "escape"
        case 0x33: "delete"
        case 0x30: "tab"
        default: nil
        }
    }

    private func mapCharacter(code: CGKeyCode) -> String? {
        let mapping: [CGKeyCode: String] = [
            0x00: "a",
            0x05: "g",
        ]
        return mapping[code]
    }

    private func modifierKeys(from flags: CGEventFlags) -> [String] {
        var keys: [String] = []
        if flags.contains(.maskCommand) { keys.append("cmd") }
        if flags.contains(.maskShift) { keys.append("shift") }
        if flags.contains(.maskAlternate) { keys.append("alt") }
        if flags.contains(.maskControl) { keys.append("ctrl") }
        if flags.contains(.maskSecondaryFn) { keys.append("fn") }
        return keys
    }

    @MainActor
    private func collectButtons(from element: Element) -> [Element] {
        var buttons: [Element] = []

        func collect(from el: Element) {
            if el.role() == "AXButton" {
                buttons.append(el)
            }

            if let children = el.children() {
                for child in children {
                    collect(from: child)
                }
            }
        }

        collect(from: element)
        return buttons
    }

    private func dialogButtons(from dialog: Element) -> [DialogButton] {
        let axButtons = self.collectButtons(from: dialog)
        self.logger.debug("Found \(axButtons.count) buttons")

        return axButtons.compactMap { btn -> DialogButton? in
            guard let title = btn.title() else { return nil }
            let isEnabled = btn.isEnabled() ?? true
            let isDefault = btn.attribute(Attribute<Bool>("AXDefault")) ?? false

            return DialogButton(
                title: title,
                isEnabled: isEnabled,
                isDefault: isDefault)
        }
    }

    private func dialogTextFields(from dialog: Element) -> [DialogTextField] {
        let axTextFields = self.collectTextFields(from: dialog)
        self.logger.debug("Found \(axTextFields.count) text fields")

        return axTextFields.indexed().map { index, field in
            DialogTextField(
                title: field.title(),
                value: field.value() as? String,
                placeholder: field.attribute(Attribute<String>("AXPlaceholderValue")),
                index: index,
                isEnabled: field.isEnabled() ?? true)
        }
    }

    private func dialogStaticTexts(from dialog: Element) -> [String] {
        let axStaticTexts = dialog.children()?.filter { $0.role() == "AXStaticText" } ?? []
        let staticTexts = axStaticTexts.compactMap { $0.value() as? String }
        self.logger.debug("Found \(staticTexts.count) static texts")
        return staticTexts
    }

    private func dialogOtherElements(from dialog: Element) -> [DialogElement] {
        let otherAxElements = dialog.children()?.filter { element in
            let role = element.role() ?? ""
            return role != "AXButton" && role != "AXTextField" &&
                role != "AXTextArea" && role != "AXStaticText"
        } ?? []

        return otherAxElements.compactMap { element -> DialogElement? in
            guard let role = element.role() else { return nil }
            return DialogElement(
                role: role,
                title: element.title(),
                value: element.value() as? String)
        }
    }

    private func navigateToPath(_ filePath: String) throws {
        self.logger.debug("Navigating to path using Cmd+Shift+G")
        self.pressKey(0x05, modifiers: [.maskCommand, .maskShift]) // G
        usleep(200_000)
        try self.typeTextValue(filePath, delay: 5000)
        self.pressKey(0x24) // Return
        usleep(100_000)
    }

    private func updateFilename(_ fileName: String, in dialog: Element) throws {
        self.logger.debug("Setting filename in dialog")
        let textFields = self.collectTextFields(from: dialog)
        guard let field = textFields.first else {
            self.logger.error("No text fields found in file dialog")
            throw PeekabooError.operationError(message: "No text fields found in dialog.")
        }

        try field.performAction(.press)
        self.pressKey(0x00, modifiers: .maskCommand)
        usleep(50000)
        try self.typeTextValue(fileName, delay: 5000)
    }

    private func clickActionButton(named actionButton: String, in dialog: Element) throws -> Bool {
        let buttons = self.collectButtons(from: dialog)
        self.logger.debug("Found \(buttons.count) buttons, looking for: \(actionButton)")

        guard let button = buttons.first(where: { $0.title() == actionButton }) else {
            return false
        }

        self.logger.debug("Clicking action button: \(actionButton)")
        try button.performAction(.press)
        return true
    }

    private func matchesDialogWindowTitle(_ title: String, expectedTitle: String?) -> Bool {
        if let expectedTitle, !expectedTitle.isEmpty {
            return title.localizedCaseInsensitiveContains(expectedTitle)
        }
        return self.dialogTitleHints.contains { title.localizedCaseInsensitiveContains($0) }
    }

    private func runningApplication(matching identifier: String) -> NSRunningApplication? {
        let lowered = identifier.lowercased()
        return NSWorkspace.shared.runningApplications.first {
            if let name = $0.localizedName?.lowercased(),
               name == lowered || name.contains(lowered)
            {
                return true
            }
            if let bundle = $0.bundleIdentifier?.lowercased(),
               bundle == lowered || bundle.contains(lowered)
            {
                return true
            }
            return false
        }
    }

    @MainActor
    private func findDialogUsingCGWindowList(title: String?) -> Element? {
        guard let cgWindows = CGWindowListCopyWindowInfo(
            [.optionOnScreenOnly, .excludeDesktopElements],
            kCGNullWindowID) as? [[String: Any]]
        else {
            return nil
        }

        for info in cgWindows {
            guard let ownerPid = info[kCGWindowOwnerPID as String] as? NSNumber else { continue }
            let windowTitle = (info[kCGWindowName as String] as? String) ?? ""

            if let expectedTitle = title,
               !windowTitle.localizedCaseInsensitiveContains(expectedTitle)
            {
                continue
            }

            if title == nil,
               !self.dialogTitleHints.contains(where: { windowTitle.localizedCaseInsensitiveContains($0) })
            {
                continue
            }

            guard let appElement = AXApp(pid: pid_t(ownerPid.intValue))?.element,
                  let windows = appElement.windowsWithTimeout()
            else { continue }

            if let matchingWindow = windows.first(where: {
                let axTitle = $0.title() ?? ""
                return axTitle == windowTitle || self.isDialogElement($0, matching: title)
            }) {
                return matchingWindow
            }
        }

        return nil
    }

    @MainActor
    private func resolveDialogCandidate(in element: Element, matching title: String?) -> Element? {
        if self.isDialogElement(element, matching: title) {
            return element
        }

        for sheet in self.sheetElements(for: element) {
            if let candidate = self.resolveDialogCandidate(in: sheet, matching: title) {
                return candidate
            }
        }

        if let children = element.children() {
            for child in children {
                if let candidate = self.resolveDialogCandidate(in: child, matching: title) {
                    return candidate
                }
            }
        }

        return nil
    }

    @MainActor
    private func sheetElements(for element: Element) -> [Element] {
        var sheets: [Element] = []
        if let children = element.children() {
            sheets.append(contentsOf: children.filter { $0.role() == "AXSheet" })
        }
        if let attachedSheets = element.sheets() {
            sheets.append(contentsOf: attachedSheets)
        }
        return sheets
    }

    @MainActor
    private func isDialogElement(_ element: Element, matching title: String?) -> Bool {
        let role = element.role() ?? ""
        let subrole = element.subrole() ?? ""
        let roleDescription = element.attribute(Attribute<String>("AXRoleDescription")) ?? ""
        let identifier = element.attribute(Attribute<String>("AXIdentifier")) ?? ""
        let windowTitle = element.title() ?? ""

        if let expectedTitle = title, !windowTitle.elementsEqual(expectedTitle) {
            return false
        }

        if role == "AXSheet" || role == "AXDialog" {
            return true
        }

        if subrole == "AXDialog" || subrole == "AXSystemDialog" || subrole == "AXAlert" ||
            subrole == "AXUnknown"
        {
            return true
        }

        if roleDescription.localizedCaseInsensitiveContains("dialog") {
            return true
        }

        if identifier.contains("NSOpenPanel") || identifier.contains("NSSavePanel") {
            return true
        }

        if self.dialogTitleHints.contains(where: { windowTitle.localizedCaseInsensitiveContains($0) }) {
            return true
        }

        return false
    }

    @MainActor
    private func isFileDialogElement(_ element: Element) -> Bool {
        let identifier = element.attribute(Attribute<String>("AXIdentifier")) ?? ""
        let windowTitle = element.title() ?? ""

        if identifier.contains("NSOpenPanel") || identifier.contains("NSSavePanel") {
            return true
        }

        return self.dialogTitleHints.contains {
            windowTitle.localizedCaseInsensitiveContains($0)
        }
    }

    @MainActor
    func typeCharacter(_ char: Character) throws {
        try DialogService.typeCharacterHandler(String(char))
    }
}

#if DEBUG
extension DialogService {
    /// Test hook to override character typing without sending real events.
    static var typeCharacterHandler: (String) throws -> Void = { text in
        try InputDriver.type(text, delayPerCharacter: 0)
    }
}
#else
extension DialogService {
    fileprivate static var typeCharacterHandler: (String) throws -> Void { { text in try InputDriver.type(
        text,
        delayPerCharacter: 0) } }
}
#endif
